package org.jing.ext.web;

import org.jing.core.lang.ExceptionHandler;
import org.jing.core.lang.JingException;
import org.jing.core.logger.JingLogger;
import org.jing.core.util.ClassUtil;
import org.jing.core.util.StringUtil;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

/**
 * Description: <br>
 *
 * @author bksmate <br>
 * @since 2024-12-24 <br>
 */
public class Sender {
    private static final JingLogger LOGGER = JingLogger.getLogger(Sender.class);

    private String charset = "UTF-8";

    private int connectTimeout = 1000 * 60 * 60;

    private int readTimeout = 1000 * 60 * 60;

    public ResponseEntity<String> get(String url) {
        return get(url, String.class);
    }

    public ResponseEntity<String> get(String url, RequestEntity<?> request) {
        return get(url, request, String.class);
    }

    public ResponseEntity<String> get(String url, Map<String, ?> variables) {
        return get(url, String.class, variables);
    }

    public <T> ResponseEntity<T> get(String url, Class<T> responseType) {
        return get(url, null, responseType, null);
    }

    public <T> ResponseEntity<T> get(String url, Class<T> responseType, Map<String, ?> variables) {
        return get(url, null, responseType, variables);
    }

    public ResponseEntity<String> get(String url, RequestEntity<?> request, Map<String, ?> variables) {
        return get(url, request, String.class, variables);
    }

    public <T> ResponseEntity<T> get(String url, RequestEntity<?> request, Class<T> responseType) {
        return get(url, request, responseType, null);
    }

    public <T> ResponseEntity<T> get(String url, RequestEntity<?> request, Class<T> responseType, Map<String, ?> variables) {
        return request(url, RequestMethod.GET, request, responseType, variables);
    }

    public ResponseEntity<String> post(String url, RequestEntity<?> request) {
        return post(url, request, String.class);
    }

    public ResponseEntity<String> post(String url) {
        return post(url, null, String.class);
    }

    public <T> ResponseEntity<T> post(String url, Class<T> responseType) {
        return post(url, null, responseType);
    }

    public <T> ResponseEntity<T> post(String url, RequestEntity<?> request, Class<T> responseType) {
        return request(url, RequestMethod.POST, request, responseType);
    }

    public <T> ResponseEntity<T> request(String url, RequestMethod method, RequestEntity<?> request, Class<T> responseType) {
        return request(url, method, request, responseType, null);
    }

    public <T> ResponseEntity<T> request(String url, RequestMethod method, RequestEntity<?> request, Class<T> responseType, Map<String, ?> variables) {
        try {
            ExceptionHandler.publishIfMatch(!WebUtil.isUrl(url), "invalid url: [{}]", url);
            if (null != variables) {
                StringBuilder stbr = new StringBuilder(url);
                boolean flag = url.contains("?") && !(url.endsWith("?") || url.endsWith("&"));
                boolean first = true;
                stbr.append(!url.contains("?") ? "?" : flag ? "&" : "");
                for (Map.Entry<String, ?> entry : variables.entrySet()) {
                    stbr.append(first ? "" : "&")
                        .append(encode4URL(entry.getKey()))
                        .append("=")
                        .append(encode4URL(ClassUtil.cast2String(entry.getValue())));
                    first = false;
                }
                url = stbr.toString();
            }
            LOGGER.debug("url: [{}]", url);
            LOGGER.debug("method: [{}]", method);
            LOGGER.debug("request: [{}]", request);

            URL path = new URL(url);
            HttpURLConnection connection = ((HttpURLConnection) (null != request && null != request.getProxy() ? path.openConnection(request.getProxy()) : path.openConnection()));
            connection.setRequestMethod(method.name());
            // 设置头
            if (null != request && null != request.getHeaders()) {
                request.getHeaders().forEach(connection::setRequestProperty);
            }
            connection.setConnectTimeout(connectTimeout);
            connection.setReadTimeout(readTimeout);
            connection.setDoOutput(true);
            if (null != request && null != request.getBody()) {
                byte[] data = toBytes(request.getBody());
                if (null != data) {
                    try (OutputStream os = connection.getOutputStream()) {
                        os.write(data);
                    }
                }
            }

            ResponseEntity<T> response = new ResponseEntity<>();
            response.setStatusCode(connection.getResponseCode());
            try (InputStream is = connection.getInputStream()) {
                response.setBody(parseBytes(toBytes(is), responseType));
            }
            response.setHeaders(connection.getHeaderFields());

            LOGGER.debug("response: [{}]", response);
            return response;
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    private byte[] toBytes(Object obj) {
        try {
            if (null == obj) {
                return null;
            }
            else if (obj instanceof byte[]) {
                return (byte[]) obj;
            }
            else if (ClassUtil.isBasicType(obj.getClass())) {
                return ClassUtil.cast2String(obj).getBytes(charset);
            }
            else if (InputStream.class.isAssignableFrom(obj.getClass())) {
                try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                    int index;
                    byte[] buffer = new byte[1024];
                    while ((index = ((InputStream) obj).read(buffer)) != -1) {
                        bos.write(buffer, 0, index);
                    }
                    return bos.toByteArray();
                }
            }
            else {
                throw new JingException("invalid type: " + obj.getClass());
            }
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T parseBytes(byte[] bytes, Class<T> clazz) {
        try {
            if (byte[].class == clazz) {
                return (T) bytes;
            }
            else if (ClassUtil.isBasicType(clazz)) {
                String string = new String(bytes, charset);
                if (String.class.equals(clazz)) {
                    return (T) string;
                }
                else if (Integer.class.equals(clazz) || int.class.equals(clazz)) {
                    return (T) Integer.valueOf(string);
                }
                else if (Long.class.equals(clazz) || long.class.equals(clazz)) {
                    return (T) Long.valueOf(string);
                }
                else if (Double.class.equals(clazz) || double.class.equals(clazz)) {
                    return (T) Double.valueOf(string);
                }
                else if (Float.class.equals(clazz) || float.class.equals(clazz)) {
                    return (T) Float.valueOf(string);
                }
                else if (Boolean.class.equals(clazz) || boolean.class.equals(clazz)) {
                    return (T) Boolean.valueOf(string);
                }
                else if (Short.class.equals(clazz) || short.class.equals(clazz)) {
                    return (T) Short.valueOf(string);
                }
                else if (Byte.class.equals(clazz) || byte.class.equals(clazz)) {
                    return (T) Byte.valueOf(string);
                }
                else if (Class.class.equals(clazz)) {
                    return (T) Class.forName(string);
                }
                else if (BigDecimal.class.equals(clazz)) {
                    return (T) new BigDecimal(string);
                }
                else if (BigInteger.class.equals(clazz)) {
                    return (T) new BigInteger(string);
                }
                else if (Character.class.equals(clazz) || char.class.equals(clazz)) {
                    return (T) new Character(string.charAt(0));
                }
                else {
                    throw new JingException("invalid type: " + clazz);
                }
            }
            else {
                throw new JingException("invalid type: " + clazz);
            }
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    public String getCharset() {
        return charset;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    private String encode4URL(String text) throws UnsupportedEncodingException {
        if (StringUtil.isEmpty(text)) {
            return "";
        }
        else {
            return URLEncoder.encode(text, charset);
        }
    }
}
